package cn.iocoder.yudao.module.signature.appconnect.util.kylinworks;

import cn.hutool.core.io.FileUtil;
import cn.iocoder.yudao.module.signature.appconnect.util.jcraft.jzlib.JZlib;
import cn.iocoder.yudao.module.signature.appconnect.util.jcraft.jzlib.ZStream;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.zip.CRC32;

/**
 * iOS PNG图片处理工具类
 * 用于处理苹果特殊格式的PNG图片
 */
@Slf4j
public class IosPngUtil {
    private static final int PNG_HEADER_SIZE = 8;
    private static final int DEFLATE_COMPRESSION_LEVEL = 9;
    private static final int BUFFER_EXTRA_SIZE = 1024;
    private static final String CGBI_CHUNK_NAME = "CgBI";
    private static final String IDAT_CHUNK_NAME = "IDAT";
    private static final String IHDR_CHUNK_NAME = "IHDR";
    private static final byte[] PNG_HEADER = {-119, 80, 78, 71, 13, 10, 26, 10};

    protected ArrayList<PNGTrunk> trunks = null;

    public static class PNGConversionException extends RuntimeException {
        public PNGConversionException(String message) {
            super(message);
        }

        public PNGConversionException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    /**
     * 转换PNG文件
     *
     * @param pngFile PNG文件
     * @return 转换后的文件路径，如果不需要转换则返回原文件路径
     */
    public String convertPNGFile(File pngFile) {
        validateParameters(pngFile);
        String newFileName = generateNewFileName(pngFile);

        try (DataInputStream file = new DataInputStream(new FileInputStream(pngFile))) {
            // 验证PNG头部
            if (!isValidPNGHeader(file)) {
                throw new PNGConversionException("Invalid PNG header");
            }

            // 解析数据块
            parsePNGChunks(file);

            // 检查是否需要转换
            if (getTrunk(CGBI_CHUNK_NAME) == null) {
                log.info("Not an Apple PNG format, no conversion needed: {}", pngFile.getName());
                return pngFile.getAbsolutePath();
            }

            // 执行转换
            return convertApplePNG(newFileName);
        } catch (Exception e) {
            throw new PNGConversionException("Failed to convert PNG file: " + pngFile.getName(), e);
        }
    }

    private void validateParameters(File pngFile) {
        if (pngFile == null) {
            throw new IllegalArgumentException("PNG file cannot be null");
        }
        if (!pngFile.exists()) {
            throw new IllegalArgumentException("PNG file does not exist: " + pngFile.getAbsolutePath());
        }
        if (!pngFile.isFile()) {
            throw new IllegalArgumentException("Not a file: " + pngFile.getAbsolutePath());
        }
        if (!isPNGFile(pngFile)) {
            throw new IllegalArgumentException("Not a PNG file: " + pngFile.getAbsolutePath());
        }
    }

    private String generateNewFileName(File pngFile) {
        String fullPath = pngFile.getAbsolutePath();
        return fullPath.substring(0, fullPath.lastIndexOf(File.separator) + 1)
                + FileUtil.getPrefix(pngFile.getName()) + "-new.png";
    }

    private boolean isValidPNGHeader(DataInputStream file) throws IOException {
        byte[] header = new byte[PNG_HEADER_SIZE];
        if (file.read(header) != PNG_HEADER_SIZE) {
            return false;
        }
        for (int i = 0; i < PNG_HEADER_SIZE; i++) {
            if (header[i] != PNG_HEADER[i]) {
                return false;
            }
        }
        return true;
    }

    private void parsePNGChunks(DataInputStream file) throws IOException {
        trunks = new ArrayList<>();
        PNGTrunk trunk;
        do {
            trunk = PNGTrunk.generateTrunk(file);
            trunks.add(trunk);
        } while (!trunk.getName().equalsIgnoreCase("IEND"));
    }

    private String convertApplePNG(String newFileName) {
        try {
            // 获取必要的数据块
            PNGTrunk dataTrunk = getTrunk(IDAT_CHUNK_NAME);
            PNGIHDRTrunk ihdrTrunk = (PNGIHDRTrunk) getTrunk(IHDR_CHUNK_NAME);

            if (dataTrunk == null || ihdrTrunk == null) {
                throw new PNGConversionException("Missing required PNG chunks");
            }

            log.debug("Processing PNG - Width: {} Height: {}", ihdrTrunk.m_nWidth, ihdrTrunk.m_nHeight);

            // 计算缓冲区大小
            int maxInflateBuffer = calculateBufferSize(ihdrTrunk);
            byte[] outputBuffer = new byte[maxInflateBuffer];

            // 解压数据
            if (!inflateData(dataTrunk, outputBuffer, maxInflateBuffer)) {
                throw new PNGConversionException("Failed to inflate PNG data");
            }

            // 转换颜色格式
            convertColorFormat(outputBuffer, ihdrTrunk);

            // 压缩数据
            byte[] newData = deflateData(outputBuffer, maxInflateBuffer);

            // 更新CRC和数据
            updateChunkData(dataTrunk, newData);

            // 写入新文件
            writeNewPNGFile(newFileName);

            return newFileName;
        } catch (Exception e) {
            throw new PNGConversionException("Failed to convert Apple PNG", e);
        }
    }

    private int calculateBufferSize(PNGIHDRTrunk ihdrTrunk) {
        return 4 * (ihdrTrunk.m_nWidth + 1) * ihdrTrunk.m_nHeight;
    }

    private boolean inflateData(PNGTrunk dataTrunk, byte[] outputBuffer, int maxBuffer) {
        ZStream inStream = new ZStream();
        try {
            inStream.avail_in = dataTrunk.getSize();
            inStream.next_in_index = 0;
            inStream.next_in = dataTrunk.getData();
            inStream.next_out_index = 0;
            inStream.next_out = outputBuffer;
            inStream.avail_out = outputBuffer.length;

            if (inStream.inflateInit(-15) != JZlib.Z_OK) {
                return false;
            }

            int result = inStream.inflate(JZlib.Z_NO_FLUSH);
            if (result != JZlib.Z_OK && result != JZlib.Z_STREAM_END) {
                return false;
            }

            return inStream.total_out <= maxBuffer;
        } finally {
            inStream.inflateEnd();
        }
    }

    private void convertColorFormat(byte[] buffer, PNGIHDRTrunk ihdrTrunk) {
        int index = 0;
        for (int y = 0; y < ihdrTrunk.m_nHeight; y++) {
            index++; // Skip filter byte
            for (int x = 0; x < ihdrTrunk.m_nWidth; x++) {
                // Swap R and B channels
                byte temp = buffer[index];
                buffer[index] = buffer[index + 2];
                buffer[index + 2] = temp;
                index += 4;
            }
        }
    }

    private byte[] deflateData(byte[] inputBuffer, int maxInflateBuffer) {
        ZStream deStream = new ZStream();
        try {
            int maxDeflateBuffer = maxInflateBuffer + BUFFER_EXTRA_SIZE;
            byte[] deflateBuffer = new byte[maxDeflateBuffer];

            deStream.avail_in = inputBuffer.length;
            deStream.next_in_index = 0;
            deStream.next_in = inputBuffer;
            deStream.next_out_index = 0;
            deStream.next_out = deflateBuffer;
            deStream.avail_out = deflateBuffer.length;

            deStream.deflateInit(DEFLATE_COMPRESSION_LEVEL);
            deStream.deflate(JZlib.Z_FINISH);

            if (deStream.total_out > maxDeflateBuffer) {
                throw new PNGConversionException("Deflate buffer overflow");
            }

            byte[] result = new byte[(int) deStream.total_out];
            System.arraycopy(deflateBuffer, 0, result, 0, result.length);
            return result;
        } finally {
            deStream.deflateEnd();
        }
    }

    private void updateChunkData(PNGTrunk dataTrunk, byte[] newData) {
        CRC32 crc32 = new CRC32();
        crc32.update(dataTrunk.getName().getBytes());
        crc32.update(newData);
        long crcValue = crc32.getValue();

        dataTrunk.m_nData = newData;
        dataTrunk.m_nCRC[0] = (byte) ((crcValue & 0xFF000000) >> 24);
        dataTrunk.m_nCRC[1] = (byte) ((crcValue & 0xFF0000) >> 16);
        dataTrunk.m_nCRC[2] = (byte) ((crcValue & 0xFF00) >> 8);
        dataTrunk.m_nCRC[3] = (byte) (crcValue & 0xFF);
        dataTrunk.m_nSize = newData.length;
    }

    private void writeNewPNGFile(String newFileName) throws IOException {
        try (FileOutputStream outStream = new FileOutputStream(newFileName)) {
            outStream.write(PNG_HEADER);
            for (PNGTrunk trunk : trunks) {
                if (!trunk.getName().equalsIgnoreCase(CGBI_CHUNK_NAME)) {
                    trunk.writeToStream(outStream);
                }
            }
        }
    }

    protected boolean isPNGFile(File file) {
        String fileName = file.getName();
        return fileName.length() >= 5 &&
                fileName.substring(fileName.length() - 4).equalsIgnoreCase(".png");
    }

    protected PNGTrunk getTrunk(String name) {
        if (trunks == null || name == null) {
            return null;
        }

        PNGTrunk returnTrunk = null;
        for (PNGTrunk trunk : trunks) {
            if (trunk.getName().equalsIgnoreCase(name)) {
                if (returnTrunk == null) {
                    returnTrunk = trunk;
                } else {
                    returnTrunk.appendTrunk(trunk);
                }
            }
        }
        return returnTrunk;
    }
}